/*
 * Copyright (C) Hisilicon Technologies Co., Ltd. 2016-2019. All rights reserved.
 * Description : hi_ssp_st7789.c
 * Author : HiMobileCam Reference Develop Team
 * Create : 2016/11/15
 */
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/proc_fs.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/spinlock.h>

#include <linux/slab.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <linux/delay.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <linux/miscdevice.h>
#include <linux/proc_fs.h>
#include <linux/poll.h>

#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/irq.h>

#include <linux/moduleparam.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>

#include "hi_ssp_st7789.h"

#ifdef __HuaweiLite__
#include "asm/io.h"
#include "spi.h"
#else
#include <mach/io.h>/* for IO_ADDRESS */
#endif

#if defined (HI3556AV100)
#define SSP_DEV    3
#define SSP_CS     0
#elif defined(HI3559V200)
#define SSP_DEV    1
#define SSP_CS     0
#endif

#define DEFAULT_MD_LEN (128)
#define SSP_STR_LEN    (64)
#define SSP_WR_SPEED   (2000000)

int g_sspfd = -1;

static int ssp_set_reg(unsigned int addr, unsigned int value)
{
#ifdef __HuaweiLite__
    (*(volatile unsigned int *)(addr)) = value;
#else
    void* pmem = ioremap_nocache(addr, DEFAULT_MD_LEN);
    if (pmem == NULL) {
        return -1;
    }
    *(unsigned int*)pmem = Value;
    iounmap(pmem);
#endif
    return 0;
}

static int ssp_read_reg(unsigned int addr, unsigned int* value)
{
#ifdef __HuaweiLite__
    (*value) = (*(volatile unsigned int *)(addr));
#else
    void* mem = ioremap_nocache(addr, DEFAULT_MD_LEN);
    if (mem == NULL) {
        return -1;
    }

    * value = *(unsigned int*)mem;
    iounmap(pmem);
#endif
    return 0;
}

int ssp_write_register(unsigned int addr, unsigned char data, unsigned int bits_per_word)
{
    int ret = -1;
    unsigned int value = 0;
    struct spi_ioc_transfer mesg[1];
    unsigned char  tx_buf[4];
    unsigned char  rx_buf[4];
    memset(tx_buf, 0, sizeof tx_buf);
    memset(rx_buf, 0, sizeof rx_buf);
    tx_buf[1] = addr & (0xff);
    tx_buf[0] = data & 0xff;
    memset(mesg, 0, sizeof mesg);
    mesg[0].tx_buf = (__u32)tx_buf;
    mesg[0].rx_buf = (__u32)rx_buf;
    mesg[0].len = 2;
    mesg[0].cs_change = 1;

    value = bits_per_word;
    ret = ioctl(g_sspfd, SPI_IOC_WR_BITS_PER_WORD, &value);
    if (ret < 0) {
        close(g_sspfd);
        return -1;
    }

    ret = ioctl(g_sspfd, SPI_IOC_MESSAGE(1), mesg);
    if (ret != mesg[0].len) {
        close(g_sspfd);
        return -1;
    }
    return 0;

}

void spi_write_dat_16bit(unsigned short dat)
{
    unsigned int addr = 0;
    unsigned char data = 0;
    addr = dat >> 8;
    data = dat & 0xff;
    ssp_write_register(addr, data, 16);
    return;
}

void ssp_write_dat_9bit(unsigned char dat)
{
    ssp_write_register(0x1, dat, 9);
}

void ssp_write_cmd_9bit(unsigned char dat)
{
    ssp_write_register(0x0, dat, 9);
}

long ssp_lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    switch(cmd) {
        case SSP_LCD_SET_BRIGHTNESS:
        case SSP_LCD_SET_BACKLIGHT:
        case SSP_LCD_SET_DISPLAY:
        break;
        default: {
            printk("Kernel: No such ssp command %#x!\n", cmd);
            break;
        }
    }
    return 0;
}

int ssp_lcd_open(struct inode * inode, struct file * file)
{
        return 0;
}

int ssp_lcd_close(struct inode * inode, struct file * file)
{
    return 0;
}

int ssp_init(void)
{
    char ac_devfile[SSP_STR_LEN] = {0};
    unsigned int value = 0;
    int ret = 0;
    if (g_sspfd >= 0) {
        return 0;
    }
    snprintf(ac_devfile, sizeof(ac_devfile), "/dev/spidev%d.%d", SSP_DEV, SSP_CS);
    g_sspfd = open(ac_devfile, O_RDWR);
    if (g_sspfd < 0) {
        printk("Open %s error.\n", ac_devfile);
        return -1;
    }
     printk("Open %s error.\n", ac_devfile);
    value = SPI_MODE_3;
    ret = ioctl(g_sspfd, SPI_IOC_WR_MODE, &value);
    if (ret < 0) {
        printk("ioctl SPI_IOC_WR_MODE err, value = %u ret = %d\n", value, ret);
        close(g_sspfd);
        return -1;
    }

    value = SSP_WR_SPEED;
    ret = ioctl(g_sspfd, SPI_IOC_WR_MAX_SPEED_HZ, &value);
    if (ret < 0) {
        printk("ioctl SPI_IOC_WR_MAX_SPEED_HZ err, value = %u ret = %d\n", value, ret);
        close(g_sspfd);
        return -1;
    }
    return ret;
}

void ssp_deinit(void)
{
    close(g_sspfd);
}


void lcd_reset(void)
{
    int tmpvalue = 0;
    //-----------------------------------------------LED RESET----------------------------------------------//
#if defined (HI3556AV100)
    int setvalue = 0x1 << 7;
    int mask = 0x1 << 7 ;
    ssp_read_reg(0x045f2400, &tmpvalue);  /**GPIO_2_7*/
    tmpvalue &= ~(mask);
    tmpvalue |= (setvalue & mask);
    ssp_set_reg(0x045f2400, tmpvalue);

    ssp_set_reg(0x045f2200, 0x80);
    ssp_set_reg(0x045f2200, 0x0);
    udelay(1);
    ssp_set_reg(0x045f2200, 0x80);
    udelay(40 * 1000);
#elif defined (HI3559V200)
    int setvalue = 0x1 << 5;
    int mask = 0x1 << 5 ;
    ssp_read_reg(0x120d0400, &tmpvalue);  /**GPIO_0_5*/
    tmpvalue &= ~(mask);
    tmpvalue |= (setvalue & mask);
    ssp_set_reg(0x120d0400, tmpvalue);

    ssp_set_reg(0x120d0080, 0x20);
    ssp_set_reg(0x120d0080, 0x0);
    udelay(1);
    ssp_set_reg(0x120d0080, 0x20);
    udelay(40 * 1000);
#endif

}



void lcd_st7789_init_vertical(void)
{
    /*spi_9bit_setting*/
    ssp_init();
    lcd_reset();
    ssp_write_cmd_9bit(0x11);
    ssp_write_cmd_9bit(0x36);
    ssp_write_dat_9bit(0x00);
    ssp_write_cmd_9bit(0xb2);
    ssp_write_dat_9bit(0x00);
    ssp_write_dat_9bit(0x00);
    ssp_write_dat_9bit(0x00);
    ssp_write_dat_9bit(0x33);
    ssp_write_dat_9bit(0x33);
    ssp_write_cmd_9bit(0xb7);
    ssp_write_dat_9bit(0x35);
    ssp_write_cmd_9bit(0xb8);
    ssp_write_dat_9bit(0x2f);
    ssp_write_dat_9bit(0x2b);
    ssp_write_dat_9bit(0x2f);
    ssp_write_cmd_9bit(0xbb);
    ssp_write_dat_9bit(0x24);
    ssp_write_cmd_9bit(0xc0);
    ssp_write_dat_9bit(0x2C);
    ssp_write_cmd_9bit(0xc3);
    ssp_write_dat_9bit(0x20);
    ssp_write_cmd_9bit(0xc4);
    ssp_write_dat_9bit(0x3);
    ssp_write_cmd_9bit(0xc6);
    ssp_write_dat_9bit(0x11);
    ssp_write_cmd_9bit(0xd0);
    ssp_write_dat_9bit(0xa4);
    ssp_write_dat_9bit(0xa1);
    ssp_write_cmd_9bit(0xe8);
    ssp_write_dat_9bit(0x03);
    ssp_write_cmd_9bit(0xe9);
    ssp_write_dat_9bit(0x0d);
    ssp_write_dat_9bit(0x12);
    ssp_write_dat_9bit(0x00);
    ssp_write_cmd_9bit(0x21);
    ssp_write_cmd_9bit(0xB0);
    ssp_write_dat_9bit(0x11); /* set RGB interface and DE mode. */
    ssp_write_dat_9bit(0x04);
    ssp_write_dat_9bit(0x00);
    ssp_write_cmd_9bit(0xB1);
    ssp_write_dat_9bit(0xC0);
    ssp_write_dat_9bit(0x2);  /* ori 2 */
    ssp_write_dat_9bit(0x14); /* ori 14 */
    ssp_write_cmd_9bit(0x3a);
    ssp_write_dat_9bit(0x55); /* 18 RGB ,55-16BIT RGB */
    ssp_write_cmd_9bit(0x11); /* Exit Sleep */
    ssp_write_cmd_9bit(0x29); /* display on */
    ssp_write_cmd_9bit(0x2c);
    return;
}



#ifdef __HuaweiLite__
static const struct file_operations_vfs ssp_lcd_fops = {
    ssp_lcd_open,   /* open */
    ssp_lcd_close,  /* close */
    0,              /* read */
    0,              /* write */
    0,              /* seek */
    ssp_lcd_ioctl   /* ioctl */
#ifndef CONFIG_DISABLE_POLL
    , 0             /* poll */
#endif
};

void lcd_sleepin(void)
{
    ssp_write_cmd_9bit(0x10);
}

void lcd_sleepout(void)
{
    ssp_write_cmd_9bit(0x11);
}

int lcd_dev_register(void)
{
    return register_driver("/dev/ssp_lcd", &ssp_lcd_fops, 0666, 0);
}

int lcd_dev_unregister(void)
{
    return unregister_driver("/dev/ssp_lcd");
}


int __init hi_ssp_lcd_init(void)
{
    int ret;
    ret = lcd_dev_register();
    if(ret != 0) {
        printk("Kernel: lcd_dev_register failed!\n");
        return -1;
    }
    lcd_st7789_init_vertical();
    printk("ssp_lcd initial ok!\n");
    return 0;

}

void __exit hi_ssp_lcd_exit(void)
{
    ssp_deinit();
    lcd_dev_unregister();
}

#else

static struct file_operations ssp_lcd_fops = {
    .owner      = THIS_MODULE,
    .unlocked_ioctl = ssp_lcd_ioctl,
    .open       = ssp_lcd_open,
    .release    = ssp_lcd_close
};


static struct miscdevice ssp_lcd_dev = {
   .minor       = MISC_DYNAMIC_MINOR,
   .name        = "ssp_lcd",
   .fops        = &ssp_lcd_fops,
};


static int __init hi_ssp_lcd_init(void)
{
    int ret = 0;
    ret = misc_register(&ssp_lcd_dev);
    if(ret != 0) {
        printk("Kernel: register ssp_0 device failed!\n");
        return -1;
    }
    lcd_st7789_init_vertical();
    printk("ssp_lcd initial ok!\n");
    return 0;
}

static void __exit hi_ssp_lcd_exit(void)
{

    ssp_deinit();
    misc_deregister(&ssp_lcd_dev);
}

module_init(hi_ssp_lcd_init);
module_exit(hi_ssp_lcd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hisilicon");
#endif
